// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{fmt::Display, str::FromStr};

use nom::{bytes::complete::{tag, take_till1, take_while1}, combinator::{all_consuming, map}, error::Error, multi::separated_list1, IResult};
use serde::{Deserialize, Serialize};

use crate::{Arch, Pkg, PkgParseError, VersionParseError};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct PkgSpec {
    #[serde(flatten)]
    pub pkg: Pkg,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub features: Vec<String>,
    #[serde(default, skip_serializing_if = "Arch::is_host")]
    pub arch: Arch,
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PkgSpecParseError {
    #[error("bad pkg")]
    BadPkg(#[from] PkgParseError),
    #[error("bad arch")]
    BadArch,
    #[error("bad version")]
    BadVersion(#[from] VersionParseError),
    #[error("bad features")]
    BadFeatures,
}

impl PkgSpec {
    pub const FEATURES_PREFIX: char = '#';
    pub const ARCH_PREFIX: char = '@';

    pub fn from_pkg_host(pkg: Pkg) -> Self {
        Self {
            pkg,
            features: Vec::new(),
            arch: Arch::host(),
        }
    }
}

impl Display for PkgSpec {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.pkg)?;
        if self.arch != Arch::host() {
            write!(f, "{}{}", PkgSpec::ARCH_PREFIX, self.arch)?;
        }
        if !self.features.is_empty() {
            write!(f, "{}", PkgSpec::FEATURES_PREFIX)?;
            for (i, feature) in self.features.iter().enumerate() {
                write!(f, "{}", feature)?;
                if i < self.features.len() - 1 {
                    write!(f, ",")?;
                }
            }
        }
        
        Ok(())
    }
}

impl FromStr for PkgSpec {
    type Err = PkgSpecParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (i, pkg) = map(
            take_till1::<_, &str, Error<_>>( |c| c == PkgSpec::FEATURES_PREFIX || c == PkgSpec::ARCH_PREFIX),
            |s: &str| Pkg::from_str(s).map_err(PkgSpecParseError::BadPkg)
        )(s).unwrap();
        let pkg = pkg?;

        let (i, arch) = if !i.is_empty() && i.as_bytes()[0] == PkgSpec::ARCH_PREFIX as u8 {
            let (_, i) = i.split_at(1);
            let (i, arch) = map(
                take_till1::<_, &str, Error<_>>(|c|
                    c == PkgSpec::FEATURES_PREFIX
                ),
                |s: &str| Arch::from_str(s).map_err(|_| PkgSpecParseError::BadArch)
            )(i).unwrap();
            (i, arch?)
        } else {
            (i, Arch::host())
        };
        
        let features = if !i.is_empty() {
            let (_, i) = i.split_at(1);
            let (_, features) = all_consuming(parse_strings_with_comma)(i)
                .map_err(|_| PkgSpecParseError::BadFeatures)?;
            features
        } else {
            Vec::new()
        };

        Ok(Self {
            pkg,
            arch,
            features,
        })
    }
}

fn parse_strings_with_comma(i: &str) -> IResult<&str, Vec<String>> {
    separated_list1(
        tag(","),
        map(
            take_while1(|c: char| c.is_alphanumeric() || c == '_'),
            |s: &str| s.to_string()
        )
    )(i)
}

#[cfg(test)]
mod test {
    use std::str::FromStr;

    use crate::{Arch, Pkg, PkgSpec, Version};

    #[test]
    fn test_pkg_spec_from_str() {
        assert_eq!(
            PkgSpec::from_str("test-1.2"),
            Ok(PkgSpec::from_pkg_host(Pkg::from_strings(
                "test".to_string(), "1.2".to_string()
            ).unwrap()))
        );
        assert_eq!(
            PkgSpec::from_str("test-1.2@aarch64"),
            Ok(PkgSpec {
                pkg: Pkg::new("test".to_string(), Version::from_major_minor(1, 2)),
                arch: Arch::AArch64,
                features: vec![],
            })
        );
        assert_eq!(
            PkgSpec::from_str("test-1.2#feature1,feature2"),
            Ok(PkgSpec {
                pkg: Pkg::new("test".to_string(), Version::from_major_minor(1, 2)),
                arch: Arch::host(),
                features: vec!["feature1".to_string(), "feature2".to_string()],
            })
        );
    }

    #[test]
    fn test_pkg_spec_display() {
        assert_eq!(
            &PkgSpec {
                pkg: Pkg::new("test".to_string(), Version::from_major_minor(1, 2)),
                arch: Arch::host(),
                features: vec![],
            }.to_string(),
            "test-1.2"
        );
        assert_eq!(
            &PkgSpec {
                pkg: Pkg::new("test".to_string(), Version::from_major_minor(1, 2)),
                arch: Arch::AArch64,
                features: vec!["feature1".to_string(), "feature2".to_string()],
            }.to_string(),
            "test-1.2@aarch64#feature1,feature2"
        );
    }

    #[test]
    fn test_pkg_spec_serialize() {
        assert_eq!(
            toml::to_string_pretty(&PkgSpec {
                pkg: Pkg::new("test".to_string(), Version::from_major_minor(1, 2)),
                arch: Arch::host(),
                features: vec![],
            }),
            Ok("name = \"test\"\n\
                version = \"1.2\"\n".to_string())
        );
        assert_eq!(
            toml::to_string_pretty(&PkgSpec {
                pkg: Pkg::new("test".to_string(), Version::from_major_minor(1, 2)),
                arch: Arch::AArch64,
                features: vec!["feature1".to_string(), "feature2".to_string()],
            }),
            Ok("name = \"test\"\n\
                version = \"1.2\"\n\
                features = [\n    \
                    \"feature1\",\n    \
                    \"feature2\",\n\
                ]\n\
                arch = \"aarch64\"\n".to_string())
        );
    }
}